/**
 * Copyright (C) 2021 - 2031 O-Cubes Co., Ltd.
 */

/****************************************************************
 *  @file    dw_gpio.c
 *  @brief   Designware gpio driver
 *  @version v1.0
 *  @date    03. Apr. 2023
 *  @author  liuchao
 ****************************************************************/

#include "error.h"
#include "dw_gpio.h"

/** Default interrupt configuration for all gpio bits */
static DEV_GPIO_INT_CFG gpio_int_cfg_default = {
	GPIO_BITS_MASK_ALL, GPIO_INT_LEVEL_TRIG_ALL,
	GPIO_INT_ACTIVE_LOW_ALL, GPIO_INT_NO_DEBOUNCE_ALL
};

/** check expressions used in DesignWare GPIO driver implementation */
#define DW_GPIO_CHECK_EXP(EXPR, ERROR_CODE)             CHECK_EXP(EXPR, ercd, ERROR_CODE, error_exit)

#ifndef DISABLE_DEVICE_OBJECT_VALID_CHECK
/** valid check of uart info object */
#define VALID_CHK_GPIO_INFO_OBJECT(gpioinfo_obj_ptr)            {                   \
		DW_GPIO_CHECK_EXP((gpioinfo_obj_ptr) != NULL, E_OBJ);               \
		DW_GPIO_CHECK_EXP(((gpioinfo_obj_ptr)->gpio_ctrl) != NULL, E_OBJ); \
	}
#endif

static inline uint32_t dw_gpio_read_ext(DW_GPIO_PORT_PTR port)
{
	return port->regs->EXT_PORTS[port->no];
}

static inline uint32_t dw_gpio_read_dir(DW_GPIO_PORT_PTR port)
{
	return port->regs->SWPORTS[port->no].DDR;
}

static inline uint32_t dw_gpio_read_dr(DW_GPIO_PORT_PTR port)
{
	return port->regs->SWPORTS[port->no].DR;
}

static inline uint32_t dw_gpio_read_mthd(DW_GPIO_PORT_PTR port)
{
	return port->regs->INTEN;
}

static inline void dw_gpio_int_enable(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
	port->regs->INTEN |= bit_mask;
}

static inline void dw_gpio_int_disable(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
	port->regs->INTEN &= (~bit_mask);
}

static inline void dw_gpio_int_mask(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
	port->regs->INTMASK |= bit_mask;
}

static inline void dw_gpio_int_unmask(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
	port->regs->INTMASK &= (~bit_mask);
}

static inline uint32_t dw_gpio_int_read_level(DW_GPIO_PORT_PTR port)
{
	return port->regs->INTTYPE_LEVEL;
}

static inline uint32_t dw_gpio_int_read_polarity(DW_GPIO_PORT_PTR port)
{
	return port->regs->INT_POLARITY;
}

static inline uint32_t dw_gpio_int_read_debounce(DW_GPIO_PORT_PTR port)
{
	return port->regs->DEBOUNCE;
}

static inline uint32_t dw_gpio_int_read_status(DW_GPIO_PORT_PTR port)
{
	return port->regs->INTSTATUS;
}

static inline void dw_gpio_int_clear(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
	port->regs->PORTA_EOI = bit_mask;
}

static void dw_gpio_int_write_level(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t bit_level)
{
	uint32_t reg_val;

	reg_val = port->regs->INTTYPE_LEVEL;
	reg_val &= (~bit_mask);
	bit_level &= bit_mask;
	reg_val |= bit_level;

	port->regs->INTTYPE_LEVEL = reg_val;
}

static void dw_gpio_int_write_polarity(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t bit_polarity)
{
	uint32_t reg_val;

	reg_val = port->regs->INT_POLARITY;

	reg_val &= (~bit_mask);
	bit_polarity &= bit_mask;
	reg_val |= bit_polarity;

	port->regs->INT_POLARITY = reg_val;
}

static void dw_gpio_int_write_debounce(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t bit_debounce)
{
	uint32_t reg_val;

	reg_val = port->regs->DEBOUNCE;

	reg_val &= (~bit_mask);
	bit_debounce &= bit_mask;
	reg_val |= bit_debounce;

	port->regs->DEBOUNCE = reg_val;
}

static void dw_gpio_set_int_cfg(DW_GPIO_PORT_PTR port, DEV_GPIO_INT_CFG *int_cfg)
{
	dw_gpio_int_write_level(port, int_cfg->int_bit_mask, int_cfg->int_bit_type);
	dw_gpio_int_write_polarity(port, int_cfg->int_bit_mask, int_cfg->int_bit_polarity);
	dw_gpio_int_write_debounce(port, int_cfg->int_bit_mask, int_cfg->int_bit_debounce);
}

static void dw_gpio_get_int_cfg(DW_GPIO_PORT_PTR port, DEV_GPIO_INT_CFG *int_cfg)
{
	int_cfg->int_bit_type = dw_gpio_int_read_level(port) & int_cfg->int_bit_mask;
	int_cfg->int_bit_polarity = dw_gpio_int_read_polarity(port) & int_cfg->int_bit_mask;
	int_cfg->int_bit_debounce = dw_gpio_int_read_debounce(port) & int_cfg->int_bit_mask;
}

static void dw_gpio_write_dr(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t val)
{
	uint32_t temp_reg;

	temp_reg = port->regs->SWPORTS[port->no].DR;
	temp_reg &= ~bit_mask;
	val &= bit_mask;
	temp_reg |= val;

	port->regs->SWPORTS[port->no].DR = temp_reg;
}

static void dw_gpio_toggle_dr(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
	uint32_t temp_reg1, temp_reg2;

	temp_reg1 = port->regs->SWPORTS[port->no].DR;
	// save unmasked bits value
	temp_reg2 = temp_reg1 & (~bit_mask);
	// toggle masked bits value
	temp_reg1 = (~temp_reg1) & bit_mask;
	// combine both masked and unmasked bits
	temp_reg1 |= temp_reg2;

	port->regs->SWPORTS[port->no].DR = temp_reg1;
}

static void dw_gpio_write_dir(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t val)
{
	uint32_t temp_reg;

	temp_reg = port->regs->SWPORTS[port->no].DDR;
	temp_reg &= ~bit_mask;
	val &= bit_mask;
	temp_reg |= val;

	port->regs->SWPORTS[port->no].DDR = temp_reg;
}

static uint32_t dw_gpio_read_val(DW_GPIO_PORT_PTR port)
{
	uint32_t val;

	val = dw_gpio_read_ext(port) & (~dw_gpio_read_dir(port));
	val |= dw_gpio_read_dr(port) & dw_gpio_read_dir(port);

	return val;
}

/** @} end of group DEVICE_DW_GPIO_STATIC */

/* interface for DEV_GPIO */
/** Open designware gpio device with specified io direction configuration */
int32_t dw_gpio_open(DEV_GPIO *gpio_dev, uint32_t dir)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);

	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);

	DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);

	port_info_ptr->opn_cnt++;

	if (port_info_ptr->opn_cnt > 1) {               /* opened before */
		if (dir == port_info_ptr->direction) {  /* direction is the same */
			return E_OK;
		} else {                                /* open with different direction */
			return E_OPNED;
		}
	}

	//dw_gpio_write_dir(port, port->valid_bit_mask, dir);

	if (port->no == DW_GPIO_PORT_A) {
		dw_gpio_int_clear(port, DW_GPIO_MASK_ALL);
		dw_gpio_int_disable(port, DW_GPIO_MASK_ALL);
		dw_gpio_int_unmask(port, DW_GPIO_MASK_ALL);

		/* install gpio interrupt handler */
		if (port->intno != DW_GPIO_INVALID_INTNO) { // Handle Bit ISR 1-1 map to vector table
			irq_attach(port->intno, port->int_handler, NULL);
			irq_disable(port->intno);
		}

		/** Set int type, int polarity and debounce configuration to default settings of device gpio */
		dw_gpio_set_int_cfg(port, (DEV_GPIO_INT_CFG *)(&gpio_int_cfg_default));
		port_info_ptr->method = dw_gpio_read_mthd(port);
	} else {
		port_info_ptr->method = DEV_GPIO_BITS_MTHD_DEFAULT;
	}

	//dw_gpio_write_dr(port, port->valid_bit_mask, 0);

	port_info_ptr->direction = dw_gpio_read_dir(port);
	port_info_ptr->extra = NULL;
	port_info_ptr->bitofs = 0;

error_exit:
	return ercd;
}

/** Close designware gpio device */
int32_t dw_gpio_close(DEV_GPIO *gpio_dev)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);

	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);

	DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);

	DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_OK);

	port_info_ptr->opn_cnt--;
	port_info_ptr->bitofs = 0;

	if (port_info_ptr->opn_cnt == 0) {
		dw_gpio_write_dr(port, port->valid_bit_mask, 0);
		dw_gpio_write_dir(port, port->valid_bit_mask, 0);

		if (port->no == DW_GPIO_PORT_A) {
			dw_gpio_int_clear(port, DW_GPIO_MASK_ALL);
			dw_gpio_int_disable(port, DW_GPIO_MASK_ALL);

			if (port->intno != DW_GPIO_INVALID_INTNO) {
				irq_disable(port->intno);
			}
		}

		port_info_ptr->direction = 0;
		port_info_ptr->method = 0;
		port_info_ptr->extra = NULL;
	} else {
		ercd = E_OPNED;
	}

error_exit:
	return ercd;
}

/** Read designware gpio device value */
int32_t dw_gpio_read(DEV_GPIO *gpio_dev, uint32_t *val, uint32_t mask)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);

	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);

	DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);
	DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

	DW_GPIO_CHECK_EXP(val != NULL, E_PAR);

	// *val = dw_gpio_read_ext(port) & mask;
	*val = dw_gpio_read_val(port) & mask;

error_exit:
	return ercd;
}

/** Write designware gpio device value */
int32_t dw_gpio_write(DEV_GPIO *gpio_dev, uint32_t val, uint32_t mask)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);

	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);

	DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);
	DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

	dw_gpio_write_dr(port, mask, val);

error_exit:
	return ercd;
}

/** Control designware gpio device */
int32_t dw_gpio_control(DEV_GPIO *gpio_dev, uint32_t ctrl_cmd, void *param)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);

	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);

	DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);
	DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

	uint32_t val32; /** to receive unsigned int value */

	if (ctrl_cmd == GPIO_CMD_SET_BIT_DIR_INPUT) {
		val32 = *((uint32_t *)param);
		dw_gpio_write_dir(port, val32, DW_GPIO_INPUT_ALL);
		port_info_ptr->direction = dw_gpio_read_dir(port);
	} else if (ctrl_cmd == GPIO_CMD_SET_BIT_DIR_OUTPUT) {
		val32 = *((uint32_t *)param);
		dw_gpio_write_dir(port, val32, DW_GPIO_OUTPUT_ALL);
		port_info_ptr->direction = dw_gpio_read_dir(port);
	} else if (ctrl_cmd == GPIO_CMD_GET_BIT_DIR) {
		DW_GPIO_CHECK_EXP((param != NULL), E_PAR);
		port_info_ptr->direction = dw_gpio_read_dir(port);
		*((uint32_t *)param) = port_info_ptr->direction;
	} else if (ctrl_cmd == GPIO_CMD_TOGGLE_BITS) {
		val32 = *((uint32_t *)param);
		// Only mask the output bits
		val32 &= port_info_ptr->direction;
		dw_gpio_toggle_dr(port, val32);
	} else {
		DW_GPIO_CHECK_EXP(port->no == DW_GPIO_PORT_A, E_NOSPT);
		/* output pin cannot be used as interrupt */
		DEV_GPIO_INT_CFG *gpio_int_cfg;
		DEV_GPIO_BIT_ISR *port_bit_isr;

		switch (ctrl_cmd) {
			case GPIO_CMD_SET_BIT_INT_CFG:
				DW_GPIO_CHECK_EXP((param != NULL), E_PAR);
				gpio_int_cfg = (DEV_GPIO_INT_CFG *)param;
				dw_gpio_set_int_cfg(port, gpio_int_cfg);
				break;

			case GPIO_CMD_GET_BIT_INT_CFG:
				DW_GPIO_CHECK_EXP((param != NULL), E_PAR);
				gpio_int_cfg = (DEV_GPIO_INT_CFG *)param;
				/** read configuration, each bit stands for different configuration */
				dw_gpio_get_int_cfg(port, gpio_int_cfg);
				break;

			case GPIO_CMD_SET_BIT_ISR:
				DW_GPIO_CHECK_EXP((param != NULL), E_PAR);
				port_bit_isr = (DEV_GPIO_BIT_ISR *)param;

				if (port_bit_isr->int_bit_ofs < port->gpio_bit_isr->int_bit_max_cnt) {
					port->gpio_bit_isr->int_bit_handler_ptr[port_bit_isr->int_bit_ofs] = port_bit_isr->int_bit_handler;
				} else {
					ercd = E_PAR;
				}

				break;

			case GPIO_CMD_GET_BIT_ISR:
				DW_GPIO_CHECK_EXP((param != NULL), E_PAR);
				port_bit_isr = (DEV_GPIO_BIT_ISR *)param;

				if (port_bit_isr->int_bit_ofs < port->gpio_bit_isr->int_bit_max_cnt) {
					port_bit_isr->int_bit_handler = port->gpio_bit_isr->int_bit_handler_ptr[port_bit_isr->int_bit_ofs];
				} else {
					ercd = E_PAR;
				}

				break;

			case GPIO_CMD_ENA_BIT_INT:
				val32 = *((uint32_t *)param);
				dw_gpio_int_enable(port, val32);
				port_info_ptr->method = dw_gpio_read_mthd(port);

				if (port_info_ptr->method) {
					if (port->intno != DW_GPIO_INVALID_INTNO) {
						irq_enable(port->intno);
					}
				}

				break;

			case GPIO_CMD_DIS_BIT_INT:
				val32 = *((uint32_t *)param);
				dw_gpio_int_disable(port, val32);
				port_info_ptr->method = dw_gpio_read_mthd(port);

				if (port_info_ptr->method == 0) {
					if (port->intno != DW_GPIO_INVALID_INTNO) {
						irq_disable(port->intno);
					}
				}

				break;

			case GPIO_CMD_GET_BIT_MTHD:
				DW_GPIO_CHECK_EXP((param != NULL), E_PAR);
				port_info_ptr->method = dw_gpio_read_mthd(port);
				*((int32_t *)param) = port_info_ptr->method;
				break;

			default:
				ercd = E_NOSPT;
				break;
		}
	}

error_exit:
	return ercd;
}

/** designware gpio interrupt process */
int32_t dw_gpio_isr_handler(DEV_GPIO *gpio_dev, void *ptr)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);

	ptr = ptr;
	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
	DW_GPIO_CHECK_EXP(port->no == DW_GPIO_PORT_A, E_NOSPT);

	uint32_t i, gpio_bit_isr_state, offset;
	uint32_t max_int_bit_count = 0;

	/** read interrupt status */
	gpio_bit_isr_state = dw_gpio_int_read_status(port);
	offset = port->obj_no * (DW_GPIO_MAX_LINE * DW_GPIO_PORT_NUM);

	if (port->gpio_bit_isr) {
		max_int_bit_count = (port->gpio_bit_isr->int_bit_max_cnt);
	} else {
		dw_gpio_int_clear(port, gpio_bit_isr_state);
	}

	for (i = 0; i < max_int_bit_count; i++) {
		if (gpio_bit_isr_state & (1 << i)) {
			/* this bit interrupt enabled */
			port_info_ptr->bitofs = i;

			if (port->gpio_bit_isr->int_bit_handler_ptr[i]) {
				port->gpio_bit_isr->int_bit_handler_ptr[i](offset + i);
			}

			dw_gpio_int_clear(port, (1 << i)); /** clear this bit interrupt */
		}
	}

error_exit:
	return ercd;
}

/** designware gpio bit interrupt process */
int32_t dw_gpio_bit_isr_handler(DEV_GPIO *gpio_dev, uint32_t bitofs, void *ptr)
{
	int32_t ercd = E_OK;
	DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_dev->gpio_info);
	ptr = ptr;

	/* START ERROR CHECK */
	VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);

	DW_GPIO_CHECK_EXP(port->no == DW_GPIO_PORT_A, E_NOSPT);

	uint32_t gpio_bit_isr_state, offset;
	uint32_t max_int_bit_count = 0;

	/** read interrupt status */
	gpio_bit_isr_state = dw_gpio_int_read_status(port);
	offset = port->obj_no * (DW_GPIO_MAX_LINE * DW_GPIO_PORT_NUM);

	if (port->gpio_bit_isr) {
		max_int_bit_count = (port->gpio_bit_isr->int_bit_max_cnt);

		if (bitofs < max_int_bit_count) {
			if (gpio_bit_isr_state & (1 << bitofs)) {
				/* this bit interrupt enabled */
				port_info_ptr->bitofs = bitofs;

				if (port->gpio_bit_isr->int_bit_handler_ptr[bitofs]) {
					port->gpio_bit_isr->int_bit_handler_ptr[bitofs](offset + bitofs);
				}

				dw_gpio_int_clear(port, (1 << bitofs)); /** clear this bit interrupt */
			}
		}
	} else {
		dw_gpio_int_clear(port, gpio_bit_isr_state);
	}

error_exit:
	return ercd;
}

